package cn.iocoder.yudao.framework.pay.core.client.impl.epayments;

import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.epayments.model.BaseEPayResult;
import cn.iocoder.yudao.framework.pay.core.client.impl.epayments.model.EPayCheckNotifyParam;
import cn.iocoder.yudao.framework.pay.core.client.impl.epayments.model.EPayCreateTradeRefundParam;
import cn.iocoder.yudao.framework.pay.core.client.impl.epayments.model.EPayCreateTradeRefundQueryParam;
import cn.iocoder.yudao.framework.pay.core.client.impl.epayments.model.EPayCreateTradeRefundQueryResult;
import cn.iocoder.yudao.framework.pay.core.client.impl.epayments.model.EPayCreateTradeRefundResult;
import cn.iocoder.yudao.framework.pay.core.client.impl.epayments.model.EPayNotifyParam;
import cn.iocoder.yudao.framework.pay.core.client.impl.epayments.model.EPayOrderQueryParam;
import cn.iocoder.yudao.framework.pay.core.client.impl.epayments.model.EPayOrderQueryResult;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.domain.AlipayTradeFastpayRefundQueryModel;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.internal.util.HttpClientUtil;
import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

/**
 * 聚合支付的支付客户端的抽象类
 *
 * @author cai
 * @date 2024/02/26 16:44
 */
@Slf4j
public abstract class AbstractEPaymentsClient extends AbstractPayClient<EPaymentsClientConfig> {

    @Getter // 仅用于单测场景
    protected DefaultEPaymentsClient client;

    public AbstractEPaymentsClient(Long channelId, String channelCode, EPaymentsClientConfig config) {
        super(channelId, channelCode, config);
    }

    @Override
    protected void doInit() {
        this.client = new DefaultEPaymentsClient(this.config);
    }

    @Override
    protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable {
        // 1.1 构建 EPayOrderQueryParam 请求
        EPayOrderQueryParam param = new EPayOrderQueryParam();
        param.setMerchant_id(this.config.getMerchantId());
        param.setIncrement_id(outTradeNo);
        param.setNonce_str(IdUtil.getSnowflakeNextIdStr());
        // 2.1 执行请求
        EPayOrderQueryResult result = client.execute(param, EPayOrderQueryResult.class);
        if (!result.checkSuccess()) { // 不成功，例如说订单不存在
            return PayOrderRespDTO.closedOf(result.getCode(), result.getMessage(),
                outTradeNo, result);
        }
        // 2.2 解析订单的状态
        Integer status = parseStatus(result.getTrade_status());
        Assert.notNull(status, () -> {
            throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", JSON.toJSONString(result)));
        });
        // TODO: 解决channelUserId取值
        return PayOrderRespDTO.of(status, result.getTrade_no(), "", LocalDateTimeUtil.parse(result.getGmt_payment(), "yyyy-MM-dd HH:mm:ss"),
            outTradeNo, result);
    }

    @Override
    protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) throws Throwable {
        // 1.1 解析 body
        JSONObject obj = JSON.parseObject(JSON.toJSONString(params));
        // 1.2 验证签名
        client.checkSign(obj);
        // 1.3 转换成是实体类
        EPayNotifyParam notifyParam = obj.toJavaObject(EPayNotifyParam.class);
        // 请求支付方验证通知id
        this.checkNotifyId(notifyParam.getIncrement_id(), notifyParam.getNotify_id());
        // 2. 解析订单的状态
        // 只有在ePayments的交易管理中存在该笔交易，且发生了交易状态的改变，ePayments才会通过该方式发 起服务器通知（即时到账中交易状态为“等待买家付款”的状态默认是不会发送通知的）
        Integer status = parseStatus(notifyParam.getTrade_status());
        Assert.notNull(status, (Supplier<Throwable>) () -> {
            throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", body));
        });
        // TODO: 解决channelUserId取值
        return PayOrderRespDTO.of(status, notifyParam.getTrade_no(), "", LocalDateTimeUtil.parse(notifyParam.getGmt_payment(), "yyyy-MM-dd HH:mm:ss"),
            notifyParam.getIncrement_id(), body);
    }

    /**
     * 检测通知id是否正常
     *
     * @param outTradeNo
     * @param notifyId
     */
    public void checkNotifyId(String outTradeNo, String notifyId) {
        // 1.1 构建 EPayOrderQueryParam 请求
        EPayCheckNotifyParam param = new EPayCheckNotifyParam();
        param.setMerchant_id(this.config.getMerchantId());
        param.setIncrement_id(outTradeNo);
        param.setNotify_id(notifyId);
        param.setNonce_str(IdUtil.getSnowflakeNextIdStr());
        // 2.1 执行请求
        BaseEPayResult result = client.execute(param, BaseEPayResult.class);
        if (!result.checkSuccess()) {
            throw new IllegalArgumentException(StrUtil.format("聚合支付支付回调 notify id 验证失败, notifyId: {}, outTradeNo: {}", notifyId, outTradeNo));
        }
    }

    @Override
    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
        // 1.1 构建 EPayCreateTradeRefundParam 请求
        EPayCreateTradeRefundParam param = new EPayCreateTradeRefundParam();
        param.setMerchant_id(this.config.getMerchantId());
        param.setIncrement_id(reqDTO.getOutTradeNo());
        param.setNonce_str(IdUtil.getSnowflakeNextIdStr());
        param.setCurrency(reqDTO.getCurrency());
        param.setRefund_fee(formatAmountDecimal(reqDTO.getRefundPrice()));
        param.setRefund_reason(reqDTO.getReason());

        // 2.1 执行请求
        EPayCreateTradeRefundResult result = client.execute(param, EPayCreateTradeRefundResult.class);
        if (!result.checkSuccess()) { // 不成功，例如说订单不存在
            return PayRefundRespDTO.failureOf(result.getCode(), result.getMessage(), reqDTO.getOutRefundNo(), result);
        }
        if (!ObjectUtils.equalsAny(result.getRefund_state(),  "SUCCESS")) { // 退款状态不是成功
            return PayRefundRespDTO.failureOf(result.getCode(), result.getMessage(), reqDTO.getOutRefundNo(), result);
        }
        // 2.2 创建返回结果
        // 聚合支付只要退款调用返回 SUCCESS，就认为退款成功，不需要回调。
        return PayRefundRespDTO.successOf(result.getRefund_trade_no(), LocalDateTimeUtil.parse(result.getRefund_pay_time(), "yyyy-MM-dd HH:mm:ss"),
            reqDTO.getOutRefundNo(), result);
    }

    @Override
    protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) throws Throwable {
        throw new UnsupportedOperationException("聚合支付无退款回调");
    }

    @Override
    protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws Throwable {
        // 1.1 构建 EPayCreateTradeRefundQueryParam 请求
        EPayCreateTradeRefundQueryParam param = new EPayCreateTradeRefundQueryParam();
        param.setMerchant_id(this.config.getMerchantId());
        param.setIncrement_id(outTradeNo);
        param.setNonce_str(IdUtil.getSnowflakeNextIdStr());
        param.setRefund_trade_no(outRefundNo);

        // 2.1 执行请求
        EPayCreateTradeRefundQueryResult result = client.execute(param, EPayCreateTradeRefundQueryResult.class);
        if (!result.checkSuccess()) {
            // 明确不存在的情况，应该就是失败，可进行关闭
            if (ObjectUtils.equalsAny(result.getCode(), "TRADE_HAS_CLOSE")) {
                return PayRefundRespDTO.failureOf(outRefundNo, result);
            }
            // 可能存在“ACQ.SYSTEM_ERROR”系统错误等情况，所以返回 WAIT 继续等待
            return PayRefundRespDTO.waitingOf(null, outRefundNo, result);
        }
        // 2.2 创建返回结果
        if (Objects.equals(result.getRefund_state(), "SUCCESS")) {
            return PayRefundRespDTO.successOf(null, LocalDateTimeUtil.parse(result.getCreated_at(), "yyyy-MM-dd HH:mm:ss"),
                outRefundNo, result);
        }
        return PayRefundRespDTO.waitingOf(null, outRefundNo, result);
    }

    @Override
    protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws Throwable {
        throw new UnsupportedOperationException("聚合支付不支持转账");
    }

    @Override
    protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) throws Throwable {
        throw new UnsupportedOperationException("聚合支付不支持转账");
    }

    private Integer parseStatus(String tradeStatus) {
        // 使用switch case 判断状态
        switch (tradeStatus) {
            case "WAIT_BUYER_PAY":
            case "TRADE_NOT_PAY":
                return PayOrderStatusRespEnum.WAITING.getStatus();
            case "TRADE_FINISHED":
            case "TRADE_SUCCESS":
                return PayOrderStatusRespEnum.SUCCESS.getStatus();
            case "TRADE_CLOSED":
            case "TRADE_REVOKED":
                return PayOrderStatusRespEnum.CLOSED.getStatus();
            case "TRADE_REFUND":
                return PayOrderStatusRespEnum.REFUND.getStatus();
            default:
                break;
        }
        log.info("支付状态不在处理范围内, status={}", tradeStatus);
        return null;
    }

    protected PayOrderRespDTO buildClosedPayOrderRespDTO(PayOrderUnifiedReqDTO reqDTO, BaseEPayResult response) {
        Assert.isFalse(response.checkSuccess());
        return PayOrderRespDTO.closedOf(response.getCode(), response.getMessage(),
            reqDTO.getOutTradeNo(), response);
    }

    protected String formatAmount(Integer amount) {
        return String.valueOf(amount / 100.0);
    }

    protected BigDecimal formatAmountDecimal(Integer amount) {
        return BigDecimal.valueOf(amount).divide(BigDecimal.valueOf(100.0), 2, BigDecimal.ROUND_HALF_UP);
    }

    protected abstract String getPaymentChannel();

}
